package com.haizhi.timer;

import com.haizhi.storage.DbInfo;
import com.haizhi.storage.DownloadInfo;
import com.haizhi.storage.StorageService;
import com.haizhi.storage.UploadInfo;
import com.haizhi.util.Downloader;
import com.haizhi.util.GenerateMd5;
import com.haizhi.util.HttpRequests;
import com.haizhi.util.Tools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static com.haizhi.util.JsonUtils.jsonToObject;

@Component
public class ScheduleTask {

    private Logger logger = LoggerFactory.getLogger(this.getClass());


    //获得download 文件列表
    private static String DOWNLOAD_LIST_URL;

    //获得文件MD5
    private static String MD5_URL;

    //服务端下载文件地址
    private static String DOWNLOAD_FILE_URL;

    //存储文件目录基本地址
    private static String DOWNLOAD_FILE_FOLDER;

    //临时下载目录地址信息
    private static String TEMP_FILE_FOLDER;

    //服务端上传文件地址
    private static String UPLOAD_FILE_URL;

    //上传文件目录基本地址
    private static String UPLOAD_FILE_FOLDER;

    private final StorageService storageService;

    @Autowired
    public ScheduleTask(Environment environment, StorageService storageService) {
        this.storageService = storageService;
        String base_url = environment.getProperty("request.url");

        DOWNLOAD_FILE_FOLDER = storageService.getDownLocation() + "/";
        UPLOAD_FILE_FOLDER = storageService.getUpLocation() + "/";
        TEMP_FILE_FOLDER = storageService.getTmpLocation() + "/";

        DOWNLOAD_LIST_URL = base_url + "/list";
        MD5_URL = base_url + "/md5/";
        DOWNLOAD_FILE_URL = base_url + "/download/";
        UPLOAD_FILE_URL = base_url + "/upload/";

        //定时任务执行之前 先初始化目录信息
        this.storageService.storageInit();
    }

    @Scheduled(fixedDelay = 1000 * 60 * 10)
    public void downLoadTask() {
        logger.info("开始执行下载任务...");

        //下载文件
        downLoadFiles(checkDownloadList());

        logger.info("下载任务完成...");
    }

    @Scheduled(fixedDelay = 1000 * 60 * 10)
    public void upLoadTask() {
        logger.info("开始执行上传任务...");

        //上传文件
        uploadFiles(checkUploadList());

        logger.info("上传任务完成...");
    }

    //上传文件
    private void uploadFiles(List<UploadInfo> uploadInfoList) {
        if (uploadInfoList.size() <= 0) {
            logger.info("没有需要上传的文件, 开始进入周期休眠..");
            return;
        }

        logger.info("本地上传文件列表数目: size = {}", uploadInfoList.size());
        uploadInfoList.forEach(uploadInfo -> {
            String fileName = uploadInfo.getFileName();
            String fileBase64 = uploadInfo.getFileBase64();

            String filePath = UPLOAD_FILE_FOLDER + fileName;
            String fileUrl = UPLOAD_FILE_URL + fileBase64;

            //上传文件
            logger.info("开始上传文件: {}", fileUrl);
            logger.info("文件存储路径: {}", filePath);
            logger.info("文件路径Base64: {}", fileBase64);

            String result = HttpRequests.post(fileUrl, uploadInfo.getMd5(), new File(filePath));
            if (result == null ||
                    Objects.equals(result, "") ||
                    Objects.equals(result, "null")) {
                return;
            }

            if (Objects.equals(result, "success")) {
                if (storageService.saveUpload(uploadInfo)) {
                    logger.info("{} 上传记录保存到数据库成功", fileName);
                } else {
                    logger.error("{} 上传记录保存到数据库失败", fileName);
                }
            }
            logger.info("上传 {} result: {}", fileName, result);
        });
    }

    //下载文件
    private void downLoadFiles(List<DownloadInfo> downloadInfoList) {

        if (downloadInfoList.size() <= 0) {
            logger.info("没有需要下载的文件, 开始进入周期休眠..");
            return;
        }

        downloadInfoList.forEach(downloadInfo -> {

            String md5 = downloadInfo.getMd5();
            String fileName = downloadInfo.getFileName();
            String fileBase64 = downloadInfo.getFileBase64();

            String filePathName = DOWNLOAD_FILE_FOLDER + fileName;
            String tmpPathName = TEMP_FILE_FOLDER + fileName;
            String fileUrl = DOWNLOAD_FILE_URL + fileBase64;

            Path filePath = Paths.get(filePathName);
            Path tmpPath = Paths.get(tmpPathName);

            do {
                //创建对应路径
                Tools.createDirs(filePath);
                Tools.createDirs(tmpPath);

                //下载文件
                logger.info("开始下载文件: {}", fileUrl);
                logger.info("文件下载路径: {}", tmpPathName);
                logger.info("文件存储路径: {}", filePathName);
                if (!new Downloader(fileUrl, tmpPathName).downloadPart()) {
                    logger.error("文件: {}下载失败..", fileName);
                    return;
                }

                //计算下载后的MD5信息, 如果信息不正确, 则删除不进行移动
                try {
                    logger.info("开始计算临时文件夹中 MD5 file = {}", tmpPathName);
                    String local_md5 = GenerateMd5.getHash(tmpPathName);
                    if (!Objects.equals(local_md5, md5)) {
                        logger.error("临时文件MD5计算不相等: {} remote = {} local = {}", tmpPathName, md5, local_md5);

                        //删除文件
                        Files.deleteIfExists(tmpPath);
                        logger.info("临时文件删除成功: {}", tmpPathName);
                        return;
                    }
                    logger.info("临时文件MD5计算相等: {} remote = {} local = {}", tmpPathName, md5, local_md5);
                } catch (IOException | NoSuchAlgorithmException e) {
                    logger.error("MD5计算失败: {}", fileName);
                    logger.error("ERROR", e);
                }

                //移动文件位置
                long start = System.currentTimeMillis();
                try {
                    logger.info("开始移动文件 {} -> {}", tmpPath, filePath);
                    Files.move(tmpPath, filePath, StandardCopyOption.REPLACE_EXISTING);
                } catch (IOException e) {
                    logger.error("文件移动失败: tmp = {} file = {} 删除文件", tmpPath, filePath);
                    //删除文件
                    try {
                        //分别删除 临时文件夹中的文件与下载目录中的文件..
                        if (Files.deleteIfExists(tmpPath)) {
                            logger.info("文件删除成功: {}", tmpPath);
                        }
                        if (Files.deleteIfExists(filePath)) {
                            logger.info("文件删除成功: {}", filePath);
                        }
                    } catch (IOException e1) {
                        logger.error("文件删除失败: {} {}", tmpPath, filePath);
                        logger.error("ERROR:", e1);
                    }
                    logger.error("ERROR:", e);
                    break;
                }
                logger.info("文件 {} 移动耗时: {} 毫秒", filePathName, (System.currentTimeMillis() - start));
                break;
            } while (true);
        });
    }

    //返回需要下载的文件列表
    private List<DownloadInfo> checkDownloadList() {
        List<DownloadInfo> diffList = new ArrayList<>();
        List<DownloadInfo> sameList = new ArrayList<>();

        //获取远程服务端文件索引
        String arrayStr = HttpRequests.get(DOWNLOAD_LIST_URL);
        if (arrayStr == null ||
                Objects.equals(arrayStr, "") ||
                Objects.equals(arrayStr, "null")) {
            logger.error("获得文件列表失败...");
            return diffList;
        }

        logger.info("下载文件列表: {}", arrayStr);
        List<String> fileBase64List = (List<String>) jsonToObject(arrayStr, List.class);
        assert fileBase64List != null;

        //判断是否由解析到下载列表信息
        if (fileBase64List.size() <= 0) {
            logger.info("没有获得任何下载列表信息...");
            return diffList;
        }

        logger.info("远端下载文件列表数目: size = {}", fileBase64List.size());
        long start = System.currentTimeMillis();
        fileBase64List.forEach((String fileBase64) -> {
            if (fileBase64 == null || Objects.equals(fileBase64, "")) {
                return;
            }

            String fileName = Tools.base64ToString(fileBase64);
            String md5 = HttpRequests.get(MD5_URL + fileBase64);
            if (md5 == null ||
                    Objects.equals(md5, "") ||
                    Objects.equals(md5, "null")) {
                logger.error("获得MD5失败: file = {}", fileBase64);
                return;
            }


            DownloadInfo downloadInfo = storageService.findDownloadOne(fileName);
            if (downloadInfo == null) {
                logger.info("数据库中没有查询到相关信息: {} md5 = {}", fileName, md5);
                diffList.add(new DownloadInfo(fileName, fileBase64, md5));
                return;
            }

            DownloadInfo newInfo = new DownloadInfo(fileName, fileBase64, md5);

            // 如果MD5不一致则重新加入下载队列
            if (!Objects.equals(md5, downloadInfo.getMd5())) {
                diffList.add(newInfo);
                return;
            }

            sameList.add(newInfo);
        });

        //必须在获取到列表信息的前提下才进行记录清理
        if (diffList.size() > 0 && sameList.size() > 0) {
            // 清空数据库然后把相同的部分再重新加入数据库
            if (!storageService.deleteAllDown()) {
                logger.error("删除全部下载记录数据失败...");
            } else {
                logger.info("删除全部下载记录数据成功...");
            }

            // 批量插入数据
            if (!storageService.saveAllDown(sameList)) {
                logger.error("存储全部下载记录数据失败...");
            } else {
                logger.info("存储全部下载记录数据成功...");
            }
        }

        long end = System.currentTimeMillis();
        logger.info("比较下载列表一共耗时:" + (end - start) + "毫秒");
        logger.info("差异下载文件数目: size = {}", diffList.size());
        return diffList;
    }

    //返回需要上传的列表
    private List<UploadInfo> checkUploadList() {

        List<UploadInfo> diffList = new ArrayList<>();

        //获得所有文件列表
        List<String> fileList = storageService.loadAllUpload();

        //判断文件是否已经被上传
        long start = System.currentTimeMillis();
        fileList.forEach(fileName -> {

            String fileBase64 = Tools.stringToBase64(fileName);
            String md5 = storageService.getFileMD5(UPLOAD_FILE_FOLDER + fileName);

            DbInfo storageInfo = storageService.findUploadOne(fileName);
            if (storageInfo == null) {
                diffList.add(new UploadInfo(fileName, fileBase64, md5));
                return;
            }

            //如果与重新进计算的MD5不相等, 则再次上传
            if (!Objects.equals(md5, storageInfo.getMd5())) {
                diffList.add(new UploadInfo(fileName, fileBase64, md5));
            }

        });

        long end = System.currentTimeMillis();
        logger.info("比较上传列表一共耗时:" + (end - start) + "毫秒");
        logger.info("差异上传文件数目为: size = {}", diffList.size());
        return diffList;
    }
}
